package jam4j.util;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

/**
 * An implementation of an option as an enum constant. 
 * 
 * @param <E> The implementing enum type itself.
 * @param <S> The interpreter state type associated with this enum type.
 * 
 * @see OptionInterpreter
 * 
 * @author Luke Maurer
 */
public interface OptionSpec<E extends Enum<E>, S> {
    /**
     * The long name of the option, along with any aliases.
     * 
     * @author Luke Maurer
     */
    @Retention(RUNTIME) @Target(FIELD) @Documented
    @interface Name {
        /**
         * The name, or names, of the option. If multiple names are given, the
         * first is considered the "primary" name and the others as aliases.
         */
        String[] value();
    }
    
    /**
     * The short name of the option, along with any aliases.
     * 
     * @author Luke Maurer
     */
    @Retention(RUNTIME) @Target(FIELD) @Documented
    @interface ShortName {
        /**
         * The short name, or names, of the option. If multiple names are
         * given, the first is considered the "primary" name and the others as
         * aliases.
         */
        char[] value();
    }
    
    /**
     * Specifies that this option takes an argument.
     * 
     * @author Luke Maurer
     */
    @Retention(RUNTIME) @Target(FIELD) @Documented
    @interface Argument {
        /**
         * Whether the argument is required.
         */
        boolean required() default true;
        /**
         * The name of the argument, as will appear in generated help text.
         */
        String name() default "ARG";
    }
    
    /**
     * Specifies a brief description of the option to include in generated help
     * text.
     * 
     * @author Luke Maurer
     */
    @Retention(RUNTIME) @Target(FIELD) @Documented
    @interface Description {
        /**
         * A quick summary of the meaning of the option.
         */
        String value();
    }
    
    /**
     * Specifies that the option can be given more than once.
     * 
     * @author Luke Maurer
     */
    @Retention(RUNTIME) @Target(FIELD) @Documented
    @interface Cumulative { }
    
    /**
     * Activate this option, given the current state of the interpreter, and
     * return the new state.
     * 
     * @param argument The argument to the option, or {@code null} if no
     *            argument was given.
     * @param state The current interpreter state.
     * @return The new interpreter state.
     * @throws OptionException If a malformed command line is detected.
     */
    S option(String argument, S state) throws OptionException;
    
    /**
     * Process a command-line argument (not an option). This method will be
     * called on the first enum constant (and hence should not vary by
     * constant).
     * 
     * @param value The value of the argument.
     * @param state The current state of the interpreter.
     * @return The new state.
     * @throws OptionException If a malformed command line is detected.
     */
    S argument(String value, S state) throws OptionException;
    
    /**
     * The state of the interpreter before any parsing has occurred. This
     * method will be called on the first enum constant (and hence should not
     * vary by constant).
     * 
     * @return The initial interpreter state.
     */
    S initialState();
    
    /**
     * Get the usage blurb to put at the beginning of the generated help text.
     * This method will be called on the first enum constant (and hence should
     * not vary by constant).
     * 
     * @return The usage summary.
     */
    String usage();
    
    /**
     * An exception thrown due do an invalid command line.
     * 
     * @see OptionInterpreter
     * @see OptionSpec
     * 
     * @author Luke Maurer
     */
    class OptionException extends Exception {
        public OptionException(String message) {
            super(message);
        }
    }
    
    /**
     * An exception due to an invalid argument to an option, or an argument to
     * an option that takes no argument, or a missing argument to an option
     * requiring one.
     * 
     * @author Luke Maurer
     */
    class IllegalOptionArgumentException extends OptionException {
        public final OptionSpec<?, ?> option;
        public final String argValue;
        
        public IllegalOptionArgumentException(
                OptionSpec<?, ?> option, String argValue) {
            this(option, argValue, "Error in argument to " + option + 
                    ": " + argValue);
        }

        public IllegalOptionArgumentException(
                OptionSpec<?, ?> option, String argValue, String message) {
            super(message);
            this.option = option;
            this.argValue = argValue;
        }
    }
    
    /**
     * An exception thrown when a non-cumulative option is found twice in a
     * command line.
     * 
     * @author Luke Maurer
     */
    class RedundantOptionException extends OptionException {
        public final OptionSpec<?, ?> option;
        
        public RedundantOptionException(OptionSpec<?, ?> option) {
            super("Option " + option + " cannot be specified more than once");
            this.option = option;
        }
    }
}
